package com.zynn.common.core.utils;

import com.google.common.base.Preconditions;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import redis.clients.jedis.JedisCluster;

import java.util.concurrent.TimeUnit;

/**
 * @author 刘天元
 * @description 分布式锁
 * @create 2018-07-17 18:46
 **/
@Service
public class RedisDistributeLock {
    private static final Logger LOGGER = LoggerFactory.getLogger(RedisDistributeLock.class);

    /**
     * 默认锁有效时间(单位毫秒)
     */
    private static final long DEFAULT_LOCK_EXPIRE_TIME_MS = 60000;
    /**
     * 默认睡眠时间(单位毫秒)
     */
    private static final long DEFAULT_SLEEP_TIME_MS = 100;

    /**
     * 每秒的毫秒数
     */
    private static final long MILLIS_PER_SECOND = 1000L;
    /**
     * 0值
     */
    private static final int INT_ZERO = 0;
    /**
     * 1值
     */
    private static final int INT_ONE = 1;
    /**
     * jedisCluster
     */
    @Autowired
    private JedisCluster jedisCluster;


    public boolean tryLock(String lock, long requestTimeoutMS) {
        return this.tryLock(lock, DEFAULT_LOCK_EXPIRE_TIME_MS, requestTimeoutMS);
    }

    public boolean tryLock(String lock, long lockExpireTimeMS, long requestTimeoutMS) {
        Preconditions.checkArgument(StringUtils.isNotBlank(lock), "lock invalid");
        Preconditions.checkArgument(lockExpireTimeMS > INT_ZERO, "lockExpireTimeMS invalid");
        Preconditions.checkArgument(requestTimeoutMS > INT_ZERO, "requestTimeoutMS invalid");

        while (requestTimeoutMS > INT_ZERO) {
            String expire = String.valueOf(System.currentTimeMillis() + lockExpireTimeMS + INT_ONE);

            Long result = jedisCluster.setnx(lock, expire);
            if (result > INT_ZERO) {
                //目前没有线程占用此锁
                jedisCluster.expire(lock, Long.valueOf(lockExpireTimeMS / MILLIS_PER_SECOND).intValue());
                return true;
            }
            String currentValue = jedisCluster.get(lock);
            if (currentValue == null) {
                //锁已经被其他线程删除马上重试获取锁
                continue;
            } else if (Long.parseLong(currentValue) < System.currentTimeMillis()) {
                //此处判断出锁已经超过了其有效的存活时间
                String oldValue = jedisCluster.getSet(lock, expire);
                if (oldValue == null || oldValue.equals(currentValue)) {
                    //1.如果拿到的旧值是空则说明在此线程做getSet之前已经有线程将锁删除，由于此线程getSet操作之后已经对锁设置了值，实际上相当于它已经占有了锁
                    //2.如果拿到的旧值不为空且等于前面查到的值，则说明在此线程进行getSet操作之前没有其他线程对锁设置了值,则此线程是第一个占有锁的
                    jedisCluster.expire(lock, Long.valueOf(lockExpireTimeMS / MILLIS_PER_SECOND).intValue());
                    return true;
                }
            }
            long sleepTime;
            if (requestTimeoutMS > DEFAULT_SLEEP_TIME_MS) {
                sleepTime = DEFAULT_SLEEP_TIME_MS;
                requestTimeoutMS -= DEFAULT_SLEEP_TIME_MS;
            } else {
                sleepTime = requestTimeoutMS;
                requestTimeoutMS = INT_ZERO;
            }
            try {
                TimeUnit.MILLISECONDS.sleep(sleepTime);
            } catch (InterruptedException e) {
                LOGGER.warn("分布式锁线程休眠异常{}", lock, e);
            }
        }
        return false;
    }

    public void unlock(String lock) {
        String value = jedisCluster.get(lock);
        if (null != value && Long.parseLong(value) > System.currentTimeMillis()) {
            //如果锁还存在并且还在有效时间则进行删除
            jedisCluster.del(lock);
        }
    }
}
